/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Collections.Generic;
using STSdb.Files;

namespace STSdb.Volumes
{
    /// <summary>
    /// Class is ThreadSafe
    /// </summary>
    public class VolumeChain : IVolume
    {
        private ulong quota = 0;
        private List<Volume> volumes = new List<Volume>();

        public delegate void QuotaExceedDelegate(VolumeChain sender, out string newVolumeFileName, out ulong volumeQuota);
        public event QuotaExceedDelegate QuotaExceed;

        public VolumeChain()
        {
        }

        #region IVolume Members

        public void Write(ulong position, byte[] buffer, int index, int count)
        {
            int fromVolumeID;
            int toVolumeID;
            lock (volumes)
            {
                ulong toPosition = position + (ulong)count - 1;
                if (toPosition >= Quota)
                    EnsureQuota(toPosition);

                fromVolumeID = FindVolumeID(position);
                toVolumeID = FindVolumeID(toPosition);
            }

            for (int id = fromVolumeID; id <= toVolumeID; id++)
            {
                Volume volume;
                lock (volumes)
                    volume = volumes[id];

                ulong offsetInVolume = position - volume.FromByte;
                int chunk = (int)Math.Min((uint)count, volume.ToByte - position + 1);

                volume.Write(offsetInVolume, buffer, index, chunk);

                position += (uint)chunk;
                index += chunk;
                count -= chunk;
            }
        }

        public void Read(ulong position, byte[] buffer, int index, int count)
        {
            int fromVolumeID;
            int toVolumeID;
            lock (volumes)
            {
                ulong toPosition = position + (ulong)count - 1;
                if (toPosition >= quota)
                    throw new InvalidOperationException("position");

                fromVolumeID = FindVolumeID(position);
                toVolumeID = FindVolumeID(toPosition);
            }

            for (int id = fromVolumeID; id <= toVolumeID; id++)
            {
                Volume volume;
                lock (volumes)
                    volume = volumes[id];

                ulong offsetInVolume = position - volume.FromByte;
                int chunk = (int)Math.Min((uint)count, volume.ToByte - position + 1);

                volume.Read(offsetInVolume, buffer, index, chunk);

                position += (uint)chunk;
                index += chunk;
                count -= chunk;
            }
        }

        public void Flush()
        {
            lock (volumes)
            {
                foreach (Volume volume in volumes)
                    volume.Flush();
            }
        }

        public void Close()
        {
            lock (volumes)
            {
                foreach (Volume volume in volumes)
                    volume.Close();
            }
        }

        public ulong Length
        {
            get
            {
                ulong length = 0;
                lock (volumes)
                {
                    foreach (Volume volume in volumes)
                        length += volume.Length;
                }

                return length;
            }
        }

        public ulong Quota
        {
            get
            {
                lock (volumes)
                    return quota;
            }
        }

        #endregion

        #region Volumes

        /// <summary>
        /// ThreadSafe
        /// </summary>
        private int FindVolumeID(ulong position)
        {
            int low = 0;
            int high = volumes.Count - 1;
            ulong pos = position;

            while (low <= high)
            {
                int mid = (low + high) >> 1;
                Volume volume = volumes[mid];

                if (pos > volume.ToByte)
                    low = mid + 1;
                else if (pos < volume.FromByte)
                    high = mid - 1;
                else
                    return mid;
            }

            return ~low;
        }

        private void EnsureQuota(ulong position)
        {
            while (position >= Quota)
            {
                string newVolumeFileName;
                ulong volumeQuota;
                OnQuotaExceed(out newVolumeFileName, out volumeQuota);
                if (newVolumeFileName == null)
                    throw new Exception("Reached quota limit.");

                Add(new Volume(newVolumeFileName, Quota, volumeQuota));
            }
        }

        //the instance of volume must be ThreadSafe
        public void Add(Volume volume)
        {
            lock (volumes)
            {
                if (volumes.Count > 0 && volumes[volumes.Count - 1].ToByte != volume.FromByte - 1)
                    throw new ArgumentException("volume");

                volumes.Add(volume);
                quota += volume.Quota;
            }
        }

        public virtual void OnQuotaExceed(out string newVolumeFileName, out ulong volumeQuota)
        {
            newVolumeFileName = null;
            volumeQuota = 0;
            if (QuotaExceed != null)
                QuotaExceed(this, out newVolumeFileName, out volumeQuota);
        }

        public Volume[] Volumes
        {
            get
            {
                lock (volumes)
                    return volumes.ToArray();
            }
        }

        #endregion
    }
}
